package br.edu.unifei.banded.mn;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.edu.unifei.mestrado.commons.graph.GraphWrapper;
import br.edu.unifei.mestrado.commons.graph.NodeWrapper;
import br.edu.unifei.mestrado.commons.graph.RelationshipWrapper;
import br.edu.unifei.mestrado.commons.mn.TempRelationships;

public class SetMatching {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	/**
	 * Usado para fazer commit a cada N nós novos
	 */
	private static final int COMMIT_INTERVAL = 1;

	// inicio das informacoes de insideOf
	// Node nivel mais detalhado | Id do node do nivel mais contraido
	private Map<NodeWrapper, Long> insideOf = new HashMap<NodeWrapper, Long>();

	private boolean hasInsideOf(NodeWrapper node) {
		return insideOf.containsKey(node);
	}

	private void setInsideOf(NodeWrapper node, Long coarsedNodeId) {
		insideOf.put(node, coarsedNodeId);
	}

	private Long getInsideOf(NodeWrapper node) {
		return insideOf.get(node);
	}

	public Map<NodeWrapper, Long> getInsideOf() {
		return insideOf;
	}

	// fim das informacoes de insideOf

	// inicio das informacoes de arestas ja processadas
	private Map<Long, TempRelationships> newArestas = new HashMap<Long, TempRelationships>();

	// fim das informacoes de arestas ja processadas

	public void coarseSet(GraphWrapper newGraph, int setId, Iterable<NodeWrapper> nodes,
			Iterable<RelationshipWrapper> setEdgeCutList) {

		Iterator<NodeWrapper> iter = nodes.iterator();
		NodeWrapper firstNode = iter.next();
		int weight = firstNode.getWeight();
		long coarsedNodeId = firstNode.getId();

		for (; iter.hasNext();) {
			NodeWrapper node = iter.next();

			// soma os pesos dos nodes
			weight += node.getWeight();

			// seta o insideof
			setInsideOf(node, coarsedNodeId);
		}
		// UTIL: cria o coarsed vertex com o id do primeiro node (startNode).
		NodeWrapper coarsedVertex = newGraph.createNode(firstNode.getId(), weight);
		coarsedVertex.setCoarsed(true); // indica que ele eh contraido

		logger.debug("Contraindo vertices: {} : Coarsed " + coarsedVertex.getId() + " Peso: "
				+ coarsedVertex.getWeight(), nodes);

		addArestasFromSet(setId, coarsedVertex, setEdgeCutList);
		imprimeArestasTmp(newArestas.values());
	}

	public void processNewArestas(GraphWrapper newGraph) {
		Collection<TempRelationships> arestasHandled = handleArestasParalelas();
		imprimeArestasTmp(arestasHandled);
		int i = 0;

		for (TempRelationships edge : arestasHandled) {
			// adiciona a aresta no grafo
			newGraph.createChain(edge.getId(), edge.getWeight(), edge.getStartNode(), edge.getwStartNode(),
					edge.getEndNode(), edge.getwEndNode());
			i++;
			// UTIL: Esse if é para não subir muito a memória e o GC não ocupar muito CPU
			if (i % COMMIT_INTERVAL == 0) {
				newGraph.endTransaction();
				newGraph.finish();
				logger.info("Qtd de arestas criadas: " + i);
				newGraph.beginTransaction();
			}
		}
	}

	private void addArestasFromSet(int setId, NodeWrapper coarsedVertex,
			Iterable<RelationshipWrapper> setEdgeCutList) {
		int weightFake = 1;
		// for para adicionar as arestas do set
		for (RelationshipWrapper aresta : setEdgeCutList) {// essas já são as arestas do corte

			// se a aresta ainda nao foi criada atraves de outro set
			if (!newArestas.containsKey(aresta.getId())) {
				NodeWrapper otherOld = aresta.getStartNode();
				if (otherOld.getPartition() == setId) {
					otherOld = aresta.getEndNode();
				}
				// UTIL: So adiciona uma nova aresta se o outro lado já estiver contraido
				// se não foi contraído, pode ser que ele seja agrupado através de outra aresta.
				if (hasInsideOf(otherOld)) {
					// se o vertice já foi contraido, então pega o vertice correspondente,
					long otherCoarsed = getInsideOf(otherOld);
					
					// Repassa o peso da aresta contraida para a aresta nova.
					int weight = aresta.getWeight();
					// Cria uma nova aresta com o novo node contraído e o outro node contraido anteriormente.
					TempRelationships newEdge = new TempRelationships(aresta.getId(), weight, coarsedVertex
							.getId(), coarsedVertex.getWeight(), otherCoarsed, weightFake);
	
					// add aresta
					newArestas.put(aresta.getId(), newEdge);
				}
			}
		}
	}

	private Collection<TempRelationships> handleArestasParalelas() {
		Map<Long, TempRelationships> hash = new HashMap<Long, TempRelationships>();

		for (TempRelationships are1 : newArestas.values()) {
			// UTIL: usa o vb, pois o va é sempre o coarsedVertex
			TempRelationships are2 = hash.get(are1.getEndNode());
			if (are2 == null) {
				// cria uma aresta nova, ligando o cVertex e o endNode
				TempRelationships coarsedEdge = new TempRelationships(are1.getId(), are1.getWeight(), are1
						.getStartNode(), are1.getwStartNode(), are1.getEndNode(), are1.getwEndNode());
				hash.put(are1.getEndNode(), coarsedEdge);
			} else {
				are2.sumWeight(are1.getWeight());
			}
		}
		return hash.values();
	}

	public void imprimeArestasTmp(final Iterable<TempRelationships> newArestas) {
		logger.debug("Arestas: {}", new Object() {
			@Override
			public String toString() {
				StringBuffer b = new StringBuffer();
				for (TempRelationships edge : newArestas) {
					b.append(edge + ", ");
				}
				return b.toString();
			}
		});
	}
}
